
#include "common/defer.h"
#include "optimizer_adapter/main.h"
#include "utils/pg_test.h"

extern "C" {
#include <postgres.h>
#include <fmgr.h>

#include <access/htup_details.h>
#include <nodes/pg_list.h>
#include <tcop/tcopprot.h>
#include <utils/builtins.h>

#include <server/funcapi.h>
}

extern "C" {

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(transform_query);
Datum transform_query(PG_FUNCTION_ARGS) {  // NOLINT
  char *transformed_query_tree = nullptr;

  char *sql = text_to_cstring(PG_GETARG_TEXT_PP(0));

  List *parsetree_list = pg_parse_query(sql);

  foreach_node(RawStmt, parsetree, parsetree_list) {
    char *op_tree;
    List *querytree_list = pg_analyze_and_rewrite_fixedparams(parsetree, sql, nullptr, 0, nullptr);
    foreach_node(Query, query, querytree_list) {
      if (query->commandType == CMD_UTILITY)
        elog(ERROR, "could not transform untility query");

      op_tree = optimizer::CascadesOptimizer::TransformQuery(query);
      if (transformed_query_tree == nullptr)
        transformed_query_tree = psprintf("%s", pstrdup(op_tree));
      else
        transformed_query_tree = psprintf("%s\n%s", transformed_query_tree, pstrdup(op_tree));

      pfree(op_tree);
    }
  }

  PG_RETURN_CSTRING(transformed_query_tree);
}

PG_FUNCTION_INFO_V1(cascades_test);
Datum cascades_test(PG_FUNCTION_ARGS) {
  auto *rsinfo = (ReturnSetInfo *)fcinfo->resultinfo;

  Datum values[3];
  bool nulls[4] = {false, false, false};

  InitMaterializedSRF(fcinfo, 0);

  optimizer::MemoryCounter mem_counters;
  {
    auto defer = optimizer::ScopedDefer{
        []() {
          if constexpr (PLAN_DEBUG)
            optimizer::MemoryTracer::Instance().Reset();
        },
        [&mem_counters]() {
          mem_counters.Count();
          if constexpr (PLAN_DEBUG) {
            LOG_PRINTLN("Memory: used=" UINT64_FORMAT "Bytes  allocated=" UINT64_FORMAT "Bytes",
                        (mem_counters.totalspace_ - mem_counters.freespace_), (mem_counters.totalspace_));

            if (const auto leak = optimizer::MemoryTracer::Instance().Check(); leak != 0)
              elog(WARNING, "memory leak detected: %ld bytes", leak);
          }
        }};
    foreach_node(pg_test::PGTestSuite, test, pg_test::PGTest::PGTestInstance().GetTestSuites()) {
      test->Run();
      const auto &statu = test->Status();
      values[0] = PointerGetDatum(cstring_to_text(pstrdup(statu.name_)));
      values[1] = PointerGetDatum(cstring_to_text(pstrdup(statu.status_)));
      if (statu.success_)
        nulls[2] = true;
      else {
        nulls[2] = false;
        values[2] =
            PointerGetDatum(cstring_to_text(psprintf("%s:%d (%s)", statu.location_, statu.line_, statu.details_)));
      }
      tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
      fflush(stdout);
    }
  }
  return 0;
}

void _PG_init(void) {
  optimizer::CascadesOptimizer::InitGuc();
  optimizer::CascadesOptimizer::InitHooks();
}
}